package work.linruchang.oauthresourceproject.config.interceptor;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerInterceptor;
import work.linruchang.oauthresourceproject.config.oauth.AllAuthInfo;
import work.linruchang.oauthresourceproject.config.oauth.CacheInfo;
import work.linruchang.oauthresourceproject.config.oauth.ThreadLocalRequestInfo;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.Optional;

/**
 * Bearer（token）认证
 * 第三方系统通过token获取本系统保护性资源（用户信息）
 * @author LinRuChang
 * @version 1.0
 * @date 2022/08/04
 * @since 1.8
 **/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 2)
public class BearerAuthInterceptor implements HandlerInterceptor {

    /**
     * 认证错误信息
     */
    @AllArgsConstructor
    @Getter
    enum AuthErrorEnum {

        INVALID_REQUEST(400, "invalid_request", "请求不符合Bearer认证标准"),
        INVALID_TOKEN(401, "invalid_token", "认证失败"),
        INSUFFICIENT_SCOPE(402, "insufficient_scope", "token权限不足");


        Integer errorCode;
        String error;
        String errorDescription;
    }


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        boolean authStatusFlag = false;
        AuthErrorEnum authErrorEnum = AuthErrorEnum.INVALID_TOKEN;

        String token = getCurrentRequestToken(); // 当前的请求头token
        String uri = request.getRequestURI(); // 当前的uri链接


        AllAuthInfo allAuthInfo = CacheInfo.cacheAccessTokenAllAuthInfo.get(token);
        if (StrUtil.isNotBlank(token)) {
            authStatusFlag = Optional.ofNullable(allAuthInfo)
                    .map(AllAuthInfo::getTokenInfo)
                    .map(tokenInfo -> {

                        Date tokenExpireDate = DateUtil.offset(tokenInfo.getCreateTime(), DateField.SECOND, tokenInfo.getExpires_in().intValue());


                        if (DateUtil.compare(new Date(), tokenExpireDate) < 0) {
                            return true;
                        }

                        // 隐式授权没有刷新Token
                        if (tokenInfo.getExpires_in_refresh() != null) {
                            // 刷新token、令牌token都过期则直接删除授权信息
                            Date refreshTokenExpireDate = DateUtil.offset(tokenInfo.getCreateTime(), DateField.SECOND, tokenInfo.getExpires_in_refresh().intValue());
                            if (DateUtil.compare(new Date(), refreshTokenExpireDate) < 0) {
                                CacheInfo.cacheAccessTokenAllAuthInfo.remove(token);
                            }
                        }
                        return false;
                    }).orElse(false);

        } else { // token缺失-没传或者没按规范进行装填Token
            authErrorEnum = AuthErrorEnum.INVALID_REQUEST;
        }


        // 认证失败
        if (!authStatusFlag) {
            response.setStatus(authErrorEnum.getErrorCode());
            response.setHeader("WWW-Authenticate", StrUtil.format("Bearer realm=\"admin token\";charset=UTF-8;error=\"{}\";error_description=\"{}\"", authErrorEnum.getError(), URLUtil.encode(authErrorEnum.getErrorDescription())));
            response.setHeader("Content-Type", "application/json;charset=UTF-8");
            response.getWriter().write(JSONUtil.toJsonStr(Dict.create()
                    .set("msg", authErrorEnum.getErrorDescription())
                    .set("code", authErrorEnum.getErrorCode())));
            return false;
        }
        ThreadLocalRequestInfo.setUserId(allAuthInfo.getUserId());
        return authStatusFlag;
    }

    public String getCurrentRequestToken() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String authorization = request.getHeader("Authorization");
        String token = null;

        // 请求头
        if (StrUtil.isNotBlank(authorization)) {
            List<String> authorizationInfos = StrUtil.splitTrim(authorization, StrUtil.SPACE);
            if (CollUtil.size(authorizationInfos) == 2 && StrUtil.equals(authorizationInfos.get(0), "Bearer")) {
                token = authorizationInfos.get(1);
            }
        } else if (StrUtil.equalsIgnoreCase(request.getMethod(), "GET") || (StrUtil.equalsIgnoreCase(request.getMethod(), "POST") && StrUtil.containsIgnoreCase(request.getHeader("Content-Type"), "application/x-www-form-urlencoded"))) {
            token = request.getParameter("access_token");
        }

        return Optional.ofNullable(token)
                .orElse(null);
    }

}
